﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.SqlClient;
using System.Web.Caching;
using System.Threading;
using System.Net;
using WebProxyApplication.WebCache.Entity;
using System.Configuration;
using System.Xml;

namespace WebProxyApplication
{
    public class Utils
    {

        private static readonly ReaderWriterLockSlim phishIPAddressLock = new ReaderWriterLockSlim();
        private static readonly Dictionary<IPAddress, string> phishIPAddressHostDictionary = new Dictionary<IPAddress, string>();

        private static string apiKey = "MTZjNDdlMjcyZDZiMWM3NDRlNzI4M2Vj";
        private static string phishtankApiKey = "0910f847bfa2f8f56a2331a6634f605bd658c44c44c18fbd3a76f0b514758fde";

        static int capacity = 50000;
        public static Filter<string> knownPhishFilter = new Filter<string>(capacity);
        public static Filter<string> knownSafeFilter = new Filter<string>(capacity);
        public static Filter<string> knownSafeGlobalFilter = new Filter<string>(capacity);

        /// <summary>
        /// This function determines if a URL has been reported as a phishing site to phishtank.com
        /// </summary>
        /// <param name="url">The URL of the website that is being checked.</param>
        /// <returns>True if the URL belongs to a known phishing web site, false otherwise.</returns>
        public static bool isPhishUrl(string url)
        {
            bool retVal = false;
            // Avoid un-necessary database access. False positives are extremely rare.
            if (knownPhishFilter.Contains(url)) 
                retVal = true;
            else // url is not in the bloom filter, possibly because it hasn't been checked yet.
            {
                string xmlText = "";
                string requestUrl = "http://checkurl.phishtank.com/checkurl/index.php?url=" + url + "&format=xml&app_key=" + phishtankApiKey;

                using (System.Net.WebClient client = new System.Net.WebClient())
                {
                    System.IO.Stream data = client.OpenRead(requestUrl); // get XML from phishtank in real time via http request
                    System.IO.StreamReader reader = new System.IO.StreamReader(data);
                    xmlText = reader.ReadToEnd();
                    data.Close();
                    reader.Close();
                }
                if (xmlText.Contains("<in_database>true</in_database>"))  // reported phish site
                {
                    Console.WriteLine("you hooked a phish!");
                    if (xmlText.Contains("<verified>true</verified>")) // reported AND verified phish site
                    {
                        retVal = true;
                        knownPhishFilter.Add(url); // add to bloom filter for speedy checking next time
                    }              
                }
            }
            return retVal;
        }

        /// <summary>
        /// Gets the Google PageRank value for a URL
        /// </summary>
        /// <param name="url">The URL of the website that is being checked.</param>
        /// <returns>The Google PageRank value of the website. If the PageRank is not available, returns -1.</returns>
        public static Int32 getPageRank(string url)
        {
            string baseUriString;
            UriBuilder baseUri = new UriBuilder(url);
            Int32 retVal = -1;
            int? cachedPageRank;
            string responseFromServer;
            string cacheKey;
            Cache cache = HttpContext.Current.Cache;

            baseUri.Query = String.Empty;
            baseUri.Path = "/";
            baseUriString = baseUri.Uri.GetComponents(UriComponents.Host, UriFormat.UriEscaped);

            cacheKey = "pageRankFor:" + baseUriString;
            cachedPageRank = cache[cacheKey] as int?;
            if (cachedPageRank.HasValue)
            {
                retVal = cachedPageRank.Value;
            }
            else
            {
                using (System.Net.WebClient client = new System.Net.WebClient())
                {
                    System.IO.Stream data = client.OpenRead("http://pr.webinfodb.net/pr.php?key=" + apiKey + "&url=" + baseUriString);
                    System.IO.StreamReader reader = new System.IO.StreamReader(data);
                    responseFromServer = reader.ReadToEnd();
                    data.Close();
                    reader.Close();
                }

                if (isNumeric(responseFromServer))
                    retVal = Int32.Parse(responseFromServer);

                cachedPageRank = retVal;
                // put the page rank into memory for 25 minutes.
                cache.Add(cacheKey, cachedPageRank, null, DateTime.Now.AddMinutes(25.0), Cache.NoSlidingExpiration, CacheItemPriority.BelowNormal, null);
            }

            return retVal;
        }

        /// <summary>
        /// Helper function
        /// </summary>
        /// <param name="str">A string to check if it is a number.</param>
        /// <returns>true if the given string is a number, false otherwise</returns>
        private static bool isNumeric(string str)
        {
            bool retVal = true;

            for (int i = 0; i < str.Length; i++)
            {
                if (char.IsDigit(str[i]) == false)
                {
                    retVal = false;
                    break; // exit for
                }
            }
            return retVal;
        }

        public static bool IsPhishIPAddress(IPAddress[] ipAddressArray)
        {
            string hostName;

            phishIPAddressLock.EnterReadLock();
            try
            {
                if (ipAddressArray != null)
                {
                    foreach (IPAddress ipAddress in ipAddressArray)
                    {
                        if (phishIPAddressHostDictionary.TryGetValue(ipAddress, out hostName))
                        {
                            ProxyLogger.Log.WarnFormat("Request with IP address {0} is IP address for known phishing host {1}", ipAddress, hostName);
                            return true;
                        }
                    }
                }
            }
            finally
            {
                phishIPAddressLock.ExitReadLock();
            }
            return false;
        }

        private static Thread phishBackgroundThread = null;

        public static void LoadPhishIPAddressDictionaryInBackground()
        {
            phishBackgroundThread = new Thread(new ThreadStart(Utils.LoadPhishIPAddressDictionary));
            phishBackgroundThread.Start();
            //Utils.LoadPhishIPAddressDictionary();
            ProxyLogger.Log.Info("Phish IP Address lookup started.");
        }

        public static void LoadPhishIPAddressDictionary()
        {
            string phishHost;
            string loadedHostName;
            Uri phishUri;
            List<phish_tank> phishTankList;
            List<string> distinctHostList;
            IPAddress[] ipAddressArray;

            using (WebProxyDataContext webProxyDatabase = new WebProxyDataContext(ConfigurationManager.ConnectionStrings["WebProxyConnectionString"].ConnectionString))
            {
                phishTankList = (from pt in webProxyDatabase.phish_tanks
                                 select pt).ToList<phish_tank>();
            }

            if (phishTankList.Count > 0)
            {
                distinctHostList = new List<string>();
                foreach (phish_tank phishingUrl in phishTankList)
                {
                    phishUri = new Uri(phishingUrl.url);
                    phishHost = phishUri.GetComponents(UriComponents.Host, UriFormat.UriEscaped);
                    if (!distinctHostList.Contains(phishHost))
                    {
                        distinctHostList.Add(phishHost);
                    }
                }
                ProxyLogger.Log.InfoFormat("Getting IP Address for {0} phish host(s)", distinctHostList.Count);

                foreach (string hostName in distinctHostList)
                {
                    try
                    {
                        ipAddressArray = Dns.GetHostAddresses(hostName);
                        phishIPAddressLock.EnterWriteLock();
                        try
                        {
                            foreach (IPAddress addr in ipAddressArray)
                            {
                                if (phishIPAddressHostDictionary.TryGetValue(addr, out loadedHostName))
                                {
                                    if (loadedHostName != hostName)
                                    {
                                        ProxyLogger.Log.WarnFormat("IP address {0} already loaded for host {1}, so couldn't load host {2}", addr, loadedHostName, hostName);
                                    }
                                }
                                else
                                {
                                    phishIPAddressHostDictionary.Add(addr, hostName);
                                }
                            }
                        }
                        finally
                        {
                            phishIPAddressLock.ExitWriteLock();
                        }
                    }
                    catch (Exception ex)
                    {
                        ProxyLogger.Log.Error(String.Format("Couldn't load IP address(es) for host {0}:", hostName), ex);
                    }
                }
            }
            else
            {
                ProxyLogger.Log.Warn("No items returned from the phish tank table");
            }

            if (phishBackgroundThread != null)
            {
                // we are done with the thread so set it to null
                phishBackgroundThread = null;
            }
        }
    }
}